package net.matuschek.http;

import java.net.URL;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import net.matuschek.util.MD5;

/*********************************************
	Copyright (c) 2001 by Daniel Matuschek
*******************************************/

/**
 * A HTTP document. It consists of the contents and HTTP headers.
 *
 * @author Daniel Matuschek (daniel@matuschek.net)
 * @author ptah
 * @version $Id: HttpDoc.java,v 1.11 2004/08/09 17:36:49 matuschd Exp $
 */
public class HttpDoc {
  /** The content */
  private byte[] content;

  /**
   * The HTTP header lines
   *
   * @link aggregation
   * @associates <{HttpHeader}>
   */
  private Vector<HttpHeader> httpHeader;
  
  /** place to store links of the document if necessary */
  private List links;

  private int httpReturnCode=0;
  private URL url;
  
  /** flag that indicates if this document is retrieved from cache */
  private boolean cached = false;

  private final static int HTTP_REDIRECTSTART=300;
  private final static int HTTP_REDIRECTEND=399;



  /**
   * Default constructor, initializes a new HttpDoc with
   * empty headers and no content
   */
  public HttpDoc() {
    httpHeader = new Vector<HttpHeader>();
  }
 
  /**
   * Gets the content of the document
   *
   * @return an array of bytes containing the document content. This
   * may represent text or binary data
   */
  public byte[] getContent() {
    return content;
  }
 
  /**
   * Set the content of the document
   * 
   * @param content
   */
  public void setContent(byte[] content) {
    this.content = content;
    // existing MD5 keys become invalid
    removeHeader(HttpHeader.CONTENT_MD5);
  }
  
  public void setHttpCode(String httpCode) {
    StringTokenizer st = new StringTokenizer(httpCode," ");
    // an HTTP answer must have at least 2 fields
    if (st.countTokens() < 2) {
      return;
    }
   
    st.nextToken();
    String codeStr = st.nextToken();
    
    try {
      httpReturnCode = Integer.parseInt(codeStr);
    } catch (NumberFormatException e) {
      // something is wrong !!!
    }
  }
  
  public void setHttpCode(int code) {
  	httpReturnCode = code;
  }

  /**
   * Get the Http Return-Code
   *
   * @return Http Return-Code
   */
  public int getHttpCode() {
	  return httpReturnCode;
  }

  /**
   * Add another HTTP header
   *
   * @param header an HttpHeader object to add to the lis
   * of headers
   */
  public void addHeader(HttpHeader header) {
    httpHeader.add(header);
  }
  
  /**
   * Get all HTTP header lines
   *
   * @return a Vector of HttpHeader objects
   */
  public Vector getHttpHeader() {
    return httpHeader;
  }

  /**
   * Get the HTTP header with the given name
   * @param headerName
   *
   * @return a HttpHeader with the given name or null if not found
   */
  public HttpHeader getHttpHeader(String headerName) {
	for (Iterator iter = httpHeader.iterator(); iter.hasNext();) {
		HttpHeader header = (HttpHeader) iter.next();
		if (header.getName().equals(headerName)) {
			return header;
		}
	}
	return null;
  }
  
  /**
   * Get the header value with the given name
   * @param headerName
   *
   * @return a HttpHeader.value with the given name or null if not found
   */
  public String getHeaderValue(String headerName) {
  	HttpHeader header = getHeader(headerName);
	return header != null ? header.getValue() : null;
  }

  /**
   * Set a HTTP header value with the given name (creates one if not found)
   * @param headerName
   * @param headerValue
   *
   * @return a HttpHeader.value with the given name or null if not found
   */
  public void setHeaderValue(String headerName, String headerValue) {
	  HttpHeader header = getHeader(headerName);
	  if (header == null) {
		  header = new HttpHeader(headerName, headerValue);
		  addHeader(header);
	  } else {
		  header.setValue(headerValue);
	  }
  }

  /**
   * Get the content of the Location header. This header will
   * be used for REDIRECTs.
   * 
   * @return the value of the HTTP Location header.
   */
  public String getLocation() {
    HttpHeader location= getHeader(HttpHeader.LOCATION);
    if (location == null) {
      return "";
    } else {
      return location.getValue();
    }
  }

  /**
   * Was it a redirect ?
   *
   * @return true if this document is a HTTP REDIRECT
   */
  public boolean isRedirect() {
    if ((httpReturnCode >= HTTP_REDIRECTSTART) &&
	(httpReturnCode <= HTTP_REDIRECTEND)) {
      return true;
    } else {
      return false;
    }
  }

  /**
   * Was it a "normal" document ?
   */
  public boolean isOk() {
    return (httpReturnCode == HttpConstants.HTTP_OK);
  }

  /**
   * Was it not modified ?
   */
  public boolean isNotModified() {
  	return (getHttpCode() == HttpConstants.HTTP_NOTMODIFIED);
  }
  
  /**
   * Was it not found ?
   */
  public boolean isNotFound() {
    return (httpReturnCode == HttpConstants.HTTP_NOTFOUND);
  }

  /**
   * did we get "Authorization required"
   */
  public boolean isUnauthorized() {
    return (httpReturnCode == HttpConstants.HTTP_UNAUTHORIZED);
  }

  /**
   * Gets the HttpHeader with the given name
   * 
   * @param headerName
   */
  public HttpHeader getHeader(String name) {
    for (int i=0; i<httpHeader.size(); i++) {
      HttpHeader h = (HttpHeader)httpHeader.elementAt(i);
      if (name.equalsIgnoreCase(h.getName())) {
	return h;
      }
    }   
    return null;
  }

  /**
   * Removes the HttpHeader with the given name
   * 
   * @param headerName
   */
  public HttpHeader removeHeader(String name) {
  	HttpHeader header = getHeader(name);
  	if (header != null) {
  		httpHeader.remove(header);
  	}
	return header;
  }
  
  /**
   * Get all the HTTP headers. This function is useful if you
   * don't know what headers exists and you want to have ALL
   * headers
   * 
   * @return a Vector containing HttpHeader objects
   */
  public Vector getHttpHeaders() {
    return httpHeader;
  }
  

  /**
   * is the content-type text/html ?
   * 
   * @return true if the HTTP Content-Type header has the
   * value text/html
   */
  public boolean isHTML() {
    HttpHeader ct = getHeader(HttpHeader.CONTENT_TYPE);
    if (ct==null) {
      return false;
    } else {
      if (ct.getValue().toLowerCase().startsWith("text/html")) {
	return true;
      }
    }
    return false;
  }
  
  /**
   * is this a Javascript document ?
   *
   * @return true if the Content-Type is text/x-javascript
   */
  public boolean isJavaScript(){
       HttpHeader ct = getHeader(HttpHeader.CONTENT_TYPE);
    if (ct==null) {
      return false;
    } else {
      if (ct.getValue().equalsIgnoreCase("text/x-javascript")) {
	return true;
      }
    }
    return false;
  }


  /**
   * Convert this object to a String.
   *
   * @return a String representation of this HttpDoc. Format
   * may change, therefore this should be used only for
   * logging or debugging
   */
  public String toString() {
    StringBuffer res = new StringBuffer();
  
    res.append(url.toString()+"\n\n");

    for (int i=0; i<httpHeader.size(); i++) {
      HttpHeader h = (HttpHeader)httpHeader.elementAt(i);
      res.append(h.toString());
      res.append("\n");
    }
    res.append("\n");
	if (content != null) {
    	res.append(new String(content));
	}
   
    return res.toString();
  }
  /**
   * Get the full URL where this document was retrieved from
   *
   * @return an URL object containing the location where this
   * document was retrieved from
   */
  public URL getURL() {
    return url;
  }


  /**
   * Set the location where this  document was retrieved from
   *
   * @param url the original location of this document
   */
  public void setURL(URL url) {
    this.url = url;
  }

  /**
   * Gets lastModified date as milliseconds.
   *
   * @return lastModified as milliseconds or -1 if not specified
   */
  public long getLastModifiedAsMilliSeconds() {
	String value = getHeaderValue(HttpHeader.LAST_MODIFIED);
	return value != null ? HTTPDateTool.parseDate(value) : -1;
  }
	
  /**
   * Gets date as milliseconds.
   *
   * @return date as milliseconds or -1 if not specified
   */
  public long getDateAsMilliSeconds() {
	  String value = getHeaderValue(HttpHeader.DATE);
	  return value != null ? HTTPDateTool.parseDate(value) : -1;
  }

  /**
   * Sets lastModified date in milliseconds.
   *
   * @param lastModified in milliseconds
   */
  public void setLastModified(long d) {
	  String dateString = HTTPDateTool.rfc1123Format.format(new Date(d));
	  setHeaderValue(HttpHeader.LAST_MODIFIED, dateString);
  }
	
  /**
   * Sets date in milliseconds.
   *
   * @param lastModified in milliseconds
   */
  public void setDate(long d) {
	  String dateString = HTTPDateTool.rfc1123Format.format(new Date(d));
	  setHeaderValue(HttpHeader.DATE, dateString);
  }
  
  /**
   * Calculates MD5 key for given content
   *
   * @param content
   * @return MD5 key for content
   */
  protected static String getContentMD5(byte[] content) {
	  if ((content == null) || (content.length == 0)) {
		  return "00000000000000000000000000000000";
	  }
	  MD5 md5 = new MD5();
	  md5.Update(content);
	  return md5.asHex();
  }
  
  /**
   * Gets MD5 key of document content.
   * A calculated key is stored as a header and reused 
   * in successive calls of this method.
   * 
   * @return MD5 key
   */
  public String getContentMD5() {
	  HttpHeader md5Header = getHeader(HttpHeader.CONTENT_MD5);
	  String md5;
	  if (md5Header != null) {
		  md5 = md5Header.getValue();
	  } else {
		  md5 = getContentMD5(getContent());
		  md5Header = new HttpHeader(HttpHeader.CONTENT_MD5, md5);
		  addHeader(md5Header);
	  }
	  return md5;
  }


	/**
	 * Set flag that indicates if this document is retrieved from cache
	 * @param cached
	 */
	public void setCached(boolean cached) {
		this.cached = cached;
	}
	
	/**
	 * Was this document retrieved from cache?
	 * @return cached
	 */
	public boolean isCached() {
		return cached;
	}

	/**
	 * Store calculated links of a HttpDoc.
	 * @param links
	 */
	public void setLinks(List links) {
		this.links = links;
	}

	/**
	 * Gets List of links (if set previously).
	 * @return List
	 */
	public List getLinks() {
		return links;
	}
  
}
